package org.example.seata.bs.order.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.rabbitmq.client.Channel;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.AllArgsConstructor;
import org.example.seata.bs.api.constant.order.OrderStatusEnum;
import org.example.seata.bs.api.dto.order.OrderDto;
import org.example.seata.bs.api.dto.order.OrderInfoDto;
import org.example.seata.bs.api.entity.order.Order;
import org.example.seata.bs.api.entity.order.OrderInfo;
import org.example.seata.bs.api.entity.product.Product;
import org.example.seata.bs.api.feign.product.RemoteProductService;
import org.example.seata.bs.api.service.order.impl.OrderServiceImpl;
import org.example.seata.bs.api.service.product.ProductService;
import org.example.seata.bs.api.vo.order.OrderInfoVo;
import org.example.seata.bs.api.vo.order.OrderVo;
import org.example.seata.bs.order.mapper.OrderEnhanceMapper;
import org.example.seata.bs.order.service.OrderEnhanceService;
import org.example.seata.bs.order.service.OrderInfoEnhanceService;
import org.example.seata.common.core.constant.LKC;
import org.example.seata.common.core.util.ApiResp;
import org.example.seata.common.core.util.BaseException;
import org.example.seata.common.core.util.BusOp;
import org.example.seata.common.rabbitmq.constant.ExchangeQueueRoutingKey;
import org.example.seata.common.rabbitmq.constant.OrderConst;
import org.example.seata.common.rabbitmq.util.MqUtil;
import org.example.seata.common.security.util.SecurityUtil;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @author hzq
 * @date 2021/11/17 15:55
 */
@Service
@Primary
@DS("order")
@AllArgsConstructor
public class OrderEnhanceServiceImpl extends OrderServiceImpl implements OrderEnhanceService {

    private final LockTemplate lockTemplate;
    private final RabbitTemplate rabbitTemplate;
    private final ProductService productService;
    private final RemoteProductService remoteProductService;
    private final OrderInfoEnhanceService orderInfoEnhanceService;

    @Override
    public OrderEnhanceMapper getBaseMapper() {
        return (OrderEnhanceMapper) super.getBaseMapper();
    }

    /**
     * 创建订单
     * 理论上创建订单后到支付有一个时间间隔
     * 这里可以把订单压入MQ
     * 在这个时间间隔内支付及订单成功
     * 如未在这个时间间隔内支付则自动取消订单
     * 一般间隔时间为30分钟
     *
     * @param dto 订单实体
     * @return ApiResp<OrderVo>
     */
    @Override
    @GlobalTransactional(name = "createOrder")
    public ApiResp<OrderVo> create(OrderDto dto) {

        // 封装订单详情
        List<OrderInfo> orderInfoList = computeOrderInfo(dto.getOrderInfoDtoList());

        // 创建订单信息
        Order order = createOrder(orderInfoList);

        // 保存订单
        BusOp.mustSucceed(save(order), "保存订单失败");

        // 设置订单详情的订单id
        setOrderInfoOrderId(orderInfoList, order.getOrderId());

        // 保存订单详情
        BusOp.mustSucceed(orderInfoEnhanceService.saveBatch(orderInfoList), "保存订单详情失败");

        // 减去虚拟库存
        BusOp.mustSucceed(minusProductVirtualInventory(dto.getOrderInfoDtoList()), "减去虚拟库存失败");

        // 推送到延时队列 10分钟如未支付自动取消订单
        sendOrderMessageToMq(order);

        OrderVo orderVo = new OrderVo();
        BeanUtils.copyProperties(order, orderVo);
        orderVo.setOrderInfoVoList(orderProductInfo(orderInfoList));

        return ApiResp.ofSuccess(orderVo);
    }

    /**
     * 监听订单消息队列
     *
     * @param message 消息
     * @param channel 信道
     */
    @GlobalTransactional(name = "cancelOrder")
    @RabbitListener(queues = ExchangeQueueRoutingKey.ORDER_DIRECT_QUEUE)
    public void receiveOrderMessage(Message message, Channel channel) {
        Order mess = MqUtil.parseMessage(message, Order.class);
        AtomicBoolean handlerResult = new AtomicBoolean(true);
        Optional.ofNullable(mess.getOrderId()).ifPresent(orderId -> {
            LockInfo lock = lockTemplate.lock(LKC.Order.ORDER_STATUS.getLockKey(orderId));
            try {
                Order order = getById(orderId);
                if (null != order) {
                    // 创建订单 --> 待支付
                    if (OrderStatusEnum.CREATE_ORDER.getStatus().equals(order.getOrderStatus())) {
                        // 取消订单
                        order.setOrderStatus(OrderStatusEnum.CANCEL_ORDER.getStatus());
                        order.setCancelTime(LocalDateTime.now());
                        BusOp.mustSucceed(updateById(order), "取消订单失败");
                        // 还原、加上虚拟库存
                        BusOp.mustSucceed(plusProductVirtualInventory(order.getOrderId()), "系统取消订单。还原、加上虚拟库存失败");
                    }
                }
            } catch (BaseException e) {
                log.error(e.getMessage());
                handlerResult.set(false);
            } finally {
                lockTemplate.releaseLock(lock);
            }
        });
        // 手动确认消息
        MqUtil.basicAck(handlerResult.get(), message, channel);
    }

    @Override
    public ApiResp<List<OrderVo>> pendingOrder() {
        LambdaQueryWrapper<Order> wrapper = Wrappers.<Order>lambdaQuery()
                .eq(Order::getAccountId, SecurityUtil.getAuth().getAccountId())
                .eq(Order::getOrderStatus, OrderStatusEnum.CREATE_ORDER.getStatus())
                .orderByDesc(Order::getCreateTime);
        return ApiResp.ofSuccess(vos(list(wrapper)));
    }

    @Override
    public ApiResp<List<OrderVo>> allOrder() {
        LambdaQueryWrapper<Order> wrapper = Wrappers.<Order>lambdaQuery()
                .eq(Order::getAccountId, SecurityUtil.getAuth().getAccountId())
                .orderByDesc(Order::getCreateTime);
        return ApiResp.ofSuccess(vos(list(wrapper)));
    }

    /**
     * 封装订单详情
     */
    List<OrderVo> vos(List<Order> list) {
        return list.stream().map(order -> {
            OrderVo orderVo = new OrderVo();
            BeanUtils.copyProperties(order, orderVo);
            orderVo.setOrderInfoVoList(orderProductInfo(orderVo.getOrderId()));
            return orderVo;
        }).collect(Collectors.toList());
    }

    /**
     * 封装订单产品详情
     *
     * @param orderId 订单id
     * @return 订单产品详情
     */
    List<OrderInfoVo> orderProductInfo(Integer orderId) {
        List<OrderInfo> orderInfos = orderInfoEnhanceService.listByOrderId(orderId);
        return orderProductInfo(orderInfos);
    }

    /**
     * 封装订单产品详情
     *
     * @param orderInfoList 详情列表
     * @return 订单产品详情
     */
    List<OrderInfoVo> orderProductInfo(List<OrderInfo> orderInfoList) {

        // todo 不想写远程调用接口 影响效率 这里直接通过多数据源查询

        List<Integer> productIds = orderInfoList.stream().map(OrderInfo::getProductId).collect(Collectors.toList());
        List<Product> products = productService.listByIds(productIds);
        return products.stream().map(p -> {
            OrderInfoVo infoVo = new OrderInfoVo();
            /*infoVo.setProductId(p.getProductId()).setProductName(p.getProductName()).setProductShortName(p.getProductShortName());*/
            BeanUtils.copyProperties(p, infoVo);
            for (OrderInfo info : orderInfoList) {
                if (infoVo.getProductId().equals(info.getProductId())) {
                    /*infoVo.setOrderId(info.getOrderId()).setOrderInfoId(info.getOrderInfoId()).setProductCount(info.getProductCount())
                            .setProductPrice(info.getProductPrice()).setProductTotalPrice(info.getProductTotalPrice());*/
                    BeanUtils.copyProperties(info, infoVo);
                }
            }
            return infoVo;
        }).collect(Collectors.toList());
    }

    /**
     * 设置订单支付成功
     * 即时订单刚好被定时任务过期 也设置为支付成功
     * 但是要减去虚拟库存 （因为定时任务取消订单的时候还原了虚拟库存）
     *
     * @see #receiveOrderMessage
     */
    @Override
    public boolean updateToOrderPaySuccess(Integer orderId) {
        LockInfo lock = lockTemplate.lock(LKC.Order.ORDER_STATUS.getLockKey(orderId));
        try {
            Order order = getById(orderId);
            if (null == order) {
                return false;
            }
            // 订单到期未支付 系统已自动关闭订单（关闭订单时已还原虚拟库存）
            if (OrderStatusEnum.CANCEL_ORDER.getStatus().equals(order.getOrderStatus())) {
                // 减去虚拟库存 这里不必一定成功 虚拟库存不重要
                minusProductVirtualInventory(orderId);
            }
            // 设置支付状态 支付时间
            order.setOrderStatus(OrderStatusEnum.ORDER_PAY_SUCCESS.getStatus()).setPayTime(LocalDateTime.now());
            return updateById(order);
        } finally {
            lockTemplate.releaseLock(lock);
        }
    }

    /**
     * 还原虚拟库存
     *
     * @param orderId 订单id
     * @return 操作结果
     */
    boolean plusProductVirtualInventory(Integer orderId) {
        ApiResp<Boolean> resp = remoteProductService.plusProductVirtualInventory(encapsulationOrderInfoDtoByOrderId(orderId));
        if (resp.isErrorOrIsNullData()) {
            return false;
        }
        return resp.getData();
    }

    /**
     * 通过订单id封装 订单详情dto
     *
     * @param orderId 订单id
     * @return 详情
     */
    List<OrderInfoDto> encapsulationOrderInfoDtoByOrderId(Integer orderId) {
        return orderInfoEnhanceService.listByOrderId(orderId).stream().map(info -> {
            OrderInfoDto dto = new OrderInfoDto();
            dto.setProductId(info.getProductId());
            dto.setProductCount(info.getProductCount());
            return dto;
        }).collect(Collectors.toList());
    }

    /**
     * 发送订单消息到Mq
     *
     * @param order 订单
     */
    void sendOrderMessageToMq(Order order) {
        // 减少消息体 只设置订单id
        Order message = new Order().setOrderId(order.getOrderId());
        MessageProperties messageProperties = new MessageProperties();
        // 消息10分钟过期 然后投入死信队列
        messageProperties.setExpiration(OrderConst.ORDER_EXPIRATION_TIME);
        MqUtil.sendMessageToRabbitMq(rabbitTemplate,
                ExchangeQueueRoutingKey.ORDER_DELAY_DIRECT_EXCHANGE,
                ExchangeQueueRoutingKey.ORDER_DELAY_DIRECT_QUEUE,
                message, messageProperties);
    }

    /**
     * 封装订单实体
     */
    Order createOrder(List<OrderInfo> orderInfoList) {
        return new Order()
                .setOrderNum(String.valueOf(System.currentTimeMillis()))
                .setOrderStatus(OrderStatusEnum.CREATE_ORDER.getStatus())
                .setOrderTotalPrice(computeOrderTotalPrice(orderInfoList))
                .setAccountId(SecurityUtil.getAuth().getAccountId());
    }

    /**
     * 封装订单产品详情
     */
    List<OrderInfo> computeOrderInfo(List<OrderInfoDto> orderInfoDtoList) {
        return orderInfoDtoList.stream().map(orderInfoDto -> {
            // 防止超卖 这里是指虚拟库存 加上分布式锁
            LockInfo lock = lockTemplate.lock(LKC.Product.PRODUCT_VIRTUAL_INVENTORY.getLockKey(orderInfoDto.getProductId()));
            try {
                ApiResp<Product> resp = remoteProductService.getById(orderInfoDto.getProductId());
                if (resp.isErrorOrIsNullData()) {
                    throw new BaseException("购买产品不存在");
                }
                // 虚拟库存小于购买数量时 直接购买失败
                Product product = resp.getData();
                if (product.getProductVirtualInventory().compareTo(orderInfoDto.getProductCount()) < 0) {
                    throw new BaseException(StrUtil.format("{}:库存不足", product.getProductShortName()));
                }
                OrderInfo orderInfo = new OrderInfo();
                BeanUtils.copyProperties(orderInfoDto, orderInfo);
                orderInfo.setProductPrice(product.getProductPrice());
                orderInfo.setProductTotalPrice(orderInfo.getProductPrice().multiply(orderInfo.getProductCount()));
                return orderInfo;
            } finally {
                lockTemplate.releaseLock(lock);
            }
        }).collect(Collectors.toList());
    }

    /**
     * 计算订单总价
     */
    BigDecimal computeOrderTotalPrice(List<OrderInfo> orderInfoList) {
        Optional<BigDecimal> bigDecimal = orderInfoList.stream().map(OrderInfo::getProductTotalPrice).reduce(BigDecimal::add);
        return bigDecimal.orElse(BigDecimal.ZERO);
    }

    /**
     * 设置订单详情的订单id
     */
    void setOrderInfoOrderId(List<OrderInfo> orderInfoList, Integer orderId) {
        orderInfoList.forEach(orderInfo -> orderInfo.setOrderId(orderId));
    }

    /**
     * 减去虚拟库存
     *
     * @param orderId 订单id
     */
    boolean minusProductVirtualInventory(Integer orderId) {
        return minusProductVirtualInventory(encapsulationOrderInfoDtoByOrderId(orderId));
    }

    /**
     * 减去虚拟库存
     */
    boolean minusProductVirtualInventory(List<OrderInfoDto> orderInfoDtoList) {
        ApiResp<Boolean> resp = remoteProductService.minusProductVirtualInventory(orderInfoDtoList);
        return !resp.isErrorOrIsNullData() && resp.getData();
    }
}
